/*-------------------------------------------------------------------------
 *
 * transam.c
 *	  postgres 事务（提交）日志接口例程
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/access/transam/transam.c
 *
 * NOTES
 *	  此文件包含访问方法事务系统的高级接口。
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/clog.h"
#include "access/subtrans.h"
#include "access/transam.h"
#include "utils/snapmgr.h"

/*
 * 单一项缓存，用于 TransactionLogFetch 的结果。拥有这样的缓存是值得的，
 * 因为我们经常发现自己在重复检查相同的 XID，例如在大批量插入、
 * 更新或删除后刚扫描表时。
 */
static TransactionId cachedFetchXid = InvalidTransactionId;
static XidStatus cachedFetchXidStatus;
static XLogRecPtr cachedCommitLSN;

/* 本地函数 */
static XidStatus fc_TransactionLogFetch(TransactionId fc_transactionId);


/* ----------------------------------------------------------------
 *		Postgres 日志访问方法接口
 *
 *		TransactionLogFetch
 * ----------------------------------------------------------------
 */

/*
 * TransactionLogFetch --- 获取指定事务 ID 的提交状态
 */
static XidStatus
fc_TransactionLogFetch(TransactionId fc_transactionId)
{
	XidStatus	fc_xidstatus;
	XLogRecPtr	fc_xidlsn;

	/*
	 * 在访问提交日志管理器之前，检查我们的单项缓存，以
	 * 查看我们是否刚刚检查过事务状态。
	 */
	if (TransactionIdEquals(fc_transactionId, cachedFetchXid))
		return cachedFetchXidStatus;

	/*
	 * 此外，检查事务 ID 是否为永久的。
	 */
	if (!TransactionIdIsNormal(fc_transactionId))
	{
		if (TransactionIdEquals(fc_transactionId, BootstrapTransactionId))
			return TRANSACTION_STATUS_COMMITTED;
		if (TransactionIdEquals(fc_transactionId, FrozenTransactionId))
			return TRANSACTION_STATUS_COMMITTED;
		return TRANSACTION_STATUS_ABORTED;
	}

	/*
	 * 获取事务状态。
	 */
	fc_xidstatus = TransactionIdGetStatus(fc_transactionId, &fc_xidlsn);

	/*
	 * 缓存它，但不要缓存未完成或已子提交事务的状态！
	 * 我们仅缓存保证不变的状态。
	 */
	if (fc_xidstatus != TRANSACTION_STATUS_IN_PROGRESS &&
		fc_xidstatus != TRANSACTION_STATUS_SUB_COMMITTED)
	{
		cachedFetchXid = fc_transactionId;
		cachedFetchXidStatus = fc_xidstatus;
		cachedCommitLSN = fc_xidlsn;
	}

	return fc_xidstatus;
}

/* ----------------------------------------------------------------
 *						接口函数
 *
 *		TransactionIdDidCommit
 *		TransactionIdDidAbort
 *		========
 *		   这些函数测试指定事务 ID 的事务状态。
 *
 *		TransactionIdCommitTree
 *		TransactionIdAsyncCommitTree
 *		TransactionIdAbortTree
 *		========
 *		   这些函数设置指定事务树的事务状态。
 *
 * 另请参见 TransactionIdIsInProgress，它曾经在此模块中，
 * 但现在位于 procarray.c 中。
 * ----------------------------------------------------------------
 */

/*
 * TransactionIdDidCommit
 *		仅当与标识符关联的事务已提交时为真。
 *
 * 注意：
 *		假定事务标识符有效并存在于 clog 中。
 */
bool							/* 如果给定事务已提交则为真 */
TransactionIdDidCommit(TransactionId fc_transactionId)
{
	XidStatus	fc_xidstatus;

	fc_xidstatus = fc_TransactionLogFetch(fc_transactionId);

	/*
	 * 如果它被标记为已提交，则表示它已提交。
	 */
	if (fc_xidstatus == TRANSACTION_STATUS_COMMITTED)
		return true;

	/*
	 * 如果它被标记为已子提交，我们必须递归检查父级。
	 * 但是，如果它早于 TransactionXmin，我们无法查看
	 * pg_subtrans；相反假设父级崩溃而没有清理
	 * 其子级。
	 *
	 * 最初我们断言 SubTransGetParent 的结果不为零。
	 * 然而，随着预备事务的引入，在数据库启动后可能
	 * 存在一个窗口，在此期间我们对 TransactionXmin 之后的事务没有
	 * 完整的了解。StartupSUBTRANS() 确保任何缺失的信息将被
	 * 清零。由于在正常情况下不应该发生这种情况，因此
	 * 发生警告似乎是合理的。
	 */
	if (fc_xidstatus == TRANSACTION_STATUS_SUB_COMMITTED)
	{
		TransactionId fc_parentXid;

		if (TransactionIdPrecedes(fc_transactionId, TransactionXmin))
			return false;
		fc_parentXid = SubTransGetParent(fc_transactionId);
		if (!TransactionIdIsValid(fc_parentXid))
		{
			elog(WARNING, "no pg_subtrans entry for subcommitted XID %u",
				 fc_transactionId);
			return false;
		}
		return TransactionIdDidCommit(fc_parentXid);
	}

	/*
	 * 它没有被提交。
	 */
	return false;
}

/*
 * TransactionIdDidAbort
 *		仅当与标识符关联的事务已中止时为真。
 *
 * 注意：
 *		假定事务标识符有效并存在于 clog 中。
 */
bool							/* 如果给定事务已中止则为真 */
TransactionIdDidAbort(TransactionId fc_transactionId)
{
	XidStatus	fc_xidstatus;

	fc_xidstatus = fc_TransactionLogFetch(fc_transactionId);

	/*
	 * 如果它被标记为已中止，则表示它已中止。
	 */
	if (fc_xidstatus == TRANSACTION_STATUS_ABORTED)
		return true;

	/*
	 * 如果标记为子提交，我们必须递归检查父事务。
	 * 然而，如果它早于 TransactionXmin，我们无法查看
	 * pg_subtrans；相反，假设父事务崩溃了，未能清理
	 * 其子事务。
	 */
	if (fc_xidstatus == TRANSACTION_STATUS_SUB_COMMITTED)
	{
		TransactionId fc_parentXid;

		if (TransactionIdPrecedes(fc_transactionId, TransactionXmin))
			return true;
		fc_parentXid = SubTransGetParent(fc_transactionId);
		if (!TransactionIdIsValid(fc_parentXid))
		{
			/* 请参阅 TransactionIdDidCommit 中的说明 */
			elog(WARNING, "no pg_subtrans entry for subcommitted XID %u",
				 fc_transactionId);
			return true;
		}
		return TransactionIdDidAbort(fc_parentXid);
	}

	/*
	 * 它并没有被中止。
	 */
	return false;
}

/*
 * TransactionIdCommitTree
 *		标记给定的事务及其子事务为已提交
 *
 * "xid" 是顶级事务提交，xids 数组包含它的
 * 已提交的子事务。
 *
 * 此提交操作不保证是原子的，但如果不是，subxids
 * 会首先被正确标记为子提交。
 */
void TransactionIdCommitTree(TransactionId fc_xid, int fc_nxids, TransactionId *fc_xids)
{
	TransactionIdSetTreeStatus(fc_xid, fc_nxids, fc_xids,
							   TRANSACTION_STATUS_COMMITTED,
							   InvalidXLogRecPtr);
}

/*
 * TransactionIdAsyncCommitTree
 *		与上述相同，但适用于异步提交。需要提交记录 LSN。
 */
void TransactionIdAsyncCommitTree(TransactionId fc_xid, int fc_nxids, TransactionId *fc_xids,
							 XLogRecPtr fc_lsn)
{
	TransactionIdSetTreeStatus(fc_xid, fc_nxids, fc_xids,
							   TRANSACTION_STATUS_COMMITTED, fc_lsn);
}

/*
 * TransactionIdAbortTree
 *		标记给定的事务及其子事务为已中止。
 *
 * "xid" 是顶级事务提交，xids 数组包含它的
 * 已提交的子事务。
 *
 * 我们不需要担心非原子行为，因为任何旁观者
 * 会认为所有事务都尚未提交。
 */
void TransactionIdAbortTree(TransactionId fc_xid, int fc_nxids, TransactionId *fc_xids)
{
	TransactionIdSetTreeStatus(fc_xid, fc_nxids, fc_xids,
							   TRANSACTION_STATUS_ABORTED, InvalidXLogRecPtr);
}

/*
 * TransactionIdPrecedes --- id1 在逻辑上是否 < id2？
 */
bool TransactionIdPrecedes(TransactionId fc_id1, TransactionId fc_id2)
{
	/*
	 * 如果任一 ID 是永久 XID，则我们可以直接进行无符号
	 * 比较。如果两者都是正常的，则进行模 2^32 比较。
	 */
	int32		fc_diff;

	if (!TransactionIdIsNormal(fc_id1) || !TransactionIdIsNormal(fc_id2))
		return (fc_id1 < fc_id2);

	fc_diff = (int32) (fc_id1 - fc_id2);
	return (fc_diff < 0);
}

/*
 * TransactionIdPrecedesOrEquals --- id1 在逻辑上是否 <= id2？
 */
bool TransactionIdPrecedesOrEquals(TransactionId fc_id1, TransactionId fc_id2)
{
	int32		fc_diff;

	if (!TransactionIdIsNormal(fc_id1) || !TransactionIdIsNormal(fc_id2))
		return (fc_id1 <= fc_id2);

	fc_diff = (int32) (fc_id1 - fc_id2);
	return (fc_diff <= 0);
}

/*
 * TransactionIdFollows --- id1 在逻辑上是否 > id2？
 */
bool TransactionIdFollows(TransactionId fc_id1, TransactionId fc_id2)
{
	int32		fc_diff;

	if (!TransactionIdIsNormal(fc_id1) || !TransactionIdIsNormal(fc_id2))
		return (fc_id1 > fc_id2);

	fc_diff = (int32) (fc_id1 - fc_id2);
	return (fc_diff > 0);
}

/*
 * TransactionIdFollowsOrEquals --- id1 在逻辑上是否 >= id2？
 */
bool TransactionIdFollowsOrEquals(TransactionId fc_id1, TransactionId fc_id2)
{
	int32		fc_diff;

	if (!TransactionIdIsNormal(fc_id1) || !TransactionIdIsNormal(fc_id2))
		return (fc_id1 >= fc_id2);

	fc_diff = (int32) (fc_id1 - fc_id2);
	return (fc_diff >= 0);
}


/*
 * TransactionIdLatest --- 获取主要事务及其子事务中的最新 XID
 */
TransactionId TransactionIdLatest(TransactionId fc_mainxid,
					int fc_nxids, const TransactionId *fc_xids)
{
	TransactionId fc_result;

	/*
	 * 实际上，xids[] 数组很可能是排序的，因此
	 * 我们可以通过直接取最后一个子 XID 来节省一些周期，但这
	 * 可能不是那么关键，因此不值得依赖
	 * 这个假设。但只是为了表明我们不是完全傻瓜，反向扫描
	 * 数组以避免不必要的赋值。
	 */
	fc_result = fc_mainxid;
	while (--fc_nxids >= 0)
	{
		if (TransactionIdPrecedes(fc_result, fc_xids[fc_nxids]))
			fc_result = fc_xids[fc_nxids];
	}
	return fc_result;
}


/*
 * TransactionIdGetCommitLSN
 *
 * 此函数返回一个足够晚的 LSN，能够
 * 确保如果我们刷新到返回的 LSN，我们
 * 将已经将事务的提交记录刷新到磁盘。
 *
 * 结果不一定是事务的提交记录的确切 LSN！例如，对于早已过期的事务（那些
 * clog 页面已经迁移到磁盘），我们将返回 InvalidXLogRecPtr。
 * 此外，由于我们将同一 clog 页面上的事务分组以节省
 * 存储，因此我们可能返回属于
 * 同一组的后续事务的 LSN。
 */
XLogRecPtr TransactionIdGetCommitLSN(TransactionId fc_xid)
{
	XLogRecPtr	fc_result;

	/*
	 * 目前，此函数的所有使用都是针对刚刚由 TransactionLogFetch
	 * 报告为已提交的 xids，因此我们预计检查
	 * TransactionLogFetch 的缓存通常会成功，避免额外访问共享内存。
	 */
	if (TransactionIdEquals(fc_xid, cachedFetchXid))
		return cachedCommitLSN;

	/* 特殊 XIDs 始终被认为已提交 */
	if (!TransactionIdIsNormal(fc_xid))
		return InvalidXLogRecPtr;

	/*
	 * 获取事务状态。
	 */
	(void) TransactionIdGetStatus(fc_xid, &fc_result);

	return fc_result;
}
